package netw;

import java.io.IOException;
import java.util.Arrays;
import ctrl.Control;
import ctrl.Chat;
import hypercast.*;

public class Overlay implements I_ReceiveCallback {

	private Control ctrl;
	public static I_OverlaySocket sock = null;
	private NotificationHandler cnh = null;
	public boolean connected = false;

	public Overlay(Control g) {
		ctrl = g;
	}

	// Configuration functions
	public void join(String overlayID) {
		// Configure and initialize hypercast socket
		cnh = new Notifications(this, ctrl.getUsr());
		HyperCastConfig conf = HyperCastConfig.createConfig("dtb.xml");
		conf.setOverlayID(overlayID);
		sock = conf.createOverlaySocket((I_ReceiveCallback) this, cnh);
		sock.joinOverlay();
		sock.getStreamManager().setNotificationHandler(cnh);
		// cnh.waitUntil_NODE_ISSTABLE();
		System.out.println("This node's logical address is:"
				+ sock.getLogicalAddress().toString());
		connected = true;
		ctrl.getUsr().start();
		ctrl.getWeb().start();
	}

	public void leave() {
		//sock.leaveOverlay(); 
		connected = false;
		(new Thread(new leave())).start();
	}
	
	private class leave implements Runnable {
		public void run() {
			sock.leaveOverlay();
		}
	}
	

	// Message functions
	// Sending Functions
	// To send a string
	public void sendString(String txt, int mod) {
		sendBytes(txt.getBytes(), mod);
	}

	public void broadcastString(String txt, int mod) {
		sendBytes(txt.getBytes(), mod, null);
	}
	
	// send a byte array
	public void sendBytes(byte[] data, int mod) {
		if(ctrl.getUsr().broadcastMode())
			sendBytes(data, mod, null);
		else
			sendBytes(data, mod, ctrl.getUsr().getDestNode());
	}
	
	public void sendBytes(byte[] data, int mod, I_LogicalAddress dest) {
		System.out.println("Sending " + Integer.toString(data.length) + " bytes for module " + Integer.toString(mod));
		if(!conCheck())
			return;
		if (data.length < 65000) {// just send it if it fits into one message
			// System.out.println("File size is less than 65000!: of :" +
			// data.length);
			I_OverlayMessage msg = sock.createMessage(addMod(mod, data));
			if (dest == null)
				sock.sendToAll(msg);
			else
				sock.sendToNode(msg, dest);
		} else if (data.length < 10000000)
			streamBytes(data, mod, dest); // If message is too big, stream instead
		else
			ctrl.println("System::File too large to send!");
	}
	
	private void streamBytes(byte[] data, int mod, I_LogicalAddress dest ) {
		if(!conCheck())
			return;
		StreamManager sman = sock.getStreamManager(); // get stream manager
		HCastOutputStream ostr = sman.getOutputStream();// (service); // get an

		// output stream from the manager
		if (dest == null) {
			ostr.setBroadcast();
		} else
			ostr.setUnicast(dest);
		
		try {
			ostr.write(mod);
			ostr.write(sock.getLogicalAddress().toByteArray());
			ostr.write(data); // write the byte array into the output stream,
								// any size
			ostr.flush();
			ostr.close();
			System.out.println("sending" + data.length);
		} catch (IOException e) {
			System.out.println("@Overlay - IOException: " + e.getMessage());
			System.out.println("@Overlay - Exception in sending stream");
		}
	}

	// Receiving functions
	public void ReceiveCallback(I_OverlayMessage msg) { // Called when message
		// arrives
		byte mod = msg.getPayload()[0];
		byte[] pl = remMod(msg.getPayload());
		if (msg.getSourceAddress() != sock.getLogicalAddress()) {
			switch (mod) {// message ID is set to
			// assign the appropriate
			// module
			case 1: // Text message
				String text = new String(pl, 0, pl.length);
				Chat.receive(text, ctrl);
				break;
			case 2: // Screen message
				ctrl.getSvw().receive(pl);
				break;
			case 3: // file message
				ctrl.getFs().receiveFile(pl);
				break;
			case 4: // user message
				String name = new String(pl, 0, pl.length);
				ctrl.getUsr().user(name, msg.getSourceAddress());
				break;
			case 5: // web request
				String pages = new String(pl, 0, pl.length);
				ctrl.getWeb().share(pages, msg.getSourceAddress());
				break;
			case 6: // web site
				ctrl.getWeb().receive(pl, msg.getSourceAddress().toString());
				break;
			case 7: // pass the name of the transmitted file
				ctrl.getFs().receiveFilename(pl);
				break;
			default:
				System.out.println("Unknown message recevied");
			}
		}
	}

	public void ReceiveStream() { // called when a stream arrives
		StreamManager sman = sock.getStreamManager();
		HCastInputStream istr = sman.acceptInputStream(); // get the arriving
															// stream
		try {
			int progress = -1;
			while (progress != istr.available()) { // Wait while new bytes are
				// arriving in the stream
				progress = istr.available();
				Thread.sleep(300);
			} // If nothing happended for 300ms, assume transmission has
				// finished

			int mod = istr.read();
			byte[] adr = new byte[sock.getLogicalAddress().toByteArray().length];
			istr.read(adr, 0, adr.length);
			byte[] data = new byte[istr.available()];
			istr.read(data, 0, istr.available()); // read stream into byte array
			istr.close();

			if (!Arrays.equals(adr, sock.getLogicalAddress().toByteArray())) {//Filter my own stream
				switch (mod) {// send array to approporiate module
				case 2: // Screen message
					ctrl.getSvw().receive(data);
					break;
				case 3: // file message
					ctrl.getFs().receiveFile(data);
					break;
				case 6: // Website message
					ctrl.getWeb().receive(data, adr.toString());
					break;
				default:
					System.out.println("Unknown message recevied");
				}
			}
		} catch (Exception e) {
			System.out.println("@Overlay - Exception when receiving stream");
			System.out.println("@Overlay - Exception: " + e.getMessage());
		}

	}

	// Helper functions
	public I_LogicalAddress[] Neighbours(){
		return sock.getNeighbors();
	}
	
	private boolean conCheck(){
		if(!connected){
			ctrl.println("Please connect first");
			return false;
		}
		else
			return true;
	}
	
	private byte[] addMod(int mod, byte[] data) {
		byte[] res = new byte[1 + data.length];
		res[0] = (byte) mod;
		System.arraycopy(data, 0, res, 1, data.length);
		return res;
	}

	private byte[] remMod(byte[] data) {
		byte[] res = new byte[data.length - 1];
		System.arraycopy(data, 1, res, 0, res.length);
		return res;
	}

}
